package com.diy.sigmund.diycommon.spring.aspect;

import com.diy.sigmund.diycommon.entity.constant.LogConstant;
import com.diy.sigmund.diycommon.util.json.JacksonUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.ui.Model;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.support.WebContentGenerator;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Enumeration;
import java.util.Objects;

/**
 * 控制层日志切面
 * <br>当不定义@order时,事务的优先级高于自定义切面,虽然都是Integer.MAX_VALUE
 *
 * @author ylm-sigmund
 * @since 2021/6/2 22:08
 */
@Order(7)
@Component
@Aspect
public class ControllerLogAspect {

    private static final Logger log = LoggerFactory.getLogger(ControllerLogAspect.class);

    /**
     * 虽然其他的module无法查看到被使用的方法，但是该切面可以应用到
     */
    // @Pointcut("execution(public * com.diy.sigmund.*.controller..*(..))")
    // @Pointcut("execution(public * com.diy.sigmund.diycommon.test.controller.async.*.controller(..))")
    @Pointcut("execution(public * com.diy.sigmund..controller..*(..))")
    public void pointcut() {
    }

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        log.info(LogConstant.SPRING_LOG, "控制层日志切面处理 开始");
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = Objects.isNull(attributes) ? null : attributes.getRequest();
        // 为null的场景:在controller的入口方法直接修饰@Async
        boolean hasContext = Objects.nonNull(request);

        final LocalDateTime start = LocalDateTime.now();
        Object resultMsg = new Object();
        try {

            // 接收到请求，记录请求内容
            logRequest(pjp, request, hasContext);

            resultMsg = pjp.proceed();

            return resultMsg;
        } finally {
            // 如果带事务的方法抛出异常，总是切面先捕获异常，并且吞掉了这个异常。事务无法捕获，所以无法回滚
            // 处理完请求，返回内容
            logResponse(pjp, request, start, resultMsg, hasContext);
        }
    }

    private void logResponse(ProceedingJoinPoint pjp, HttpServletRequest request, LocalDateTime start,
                             Object resultMsg, boolean hasContext) {
        if (!hasContext) {
            log.warn("当前线程没有上下文,不打印出参");
        }
        // 长度大于16KB就不打印
        final int maxLength = 16 * 1024;
        // 是否打印出参
        boolean printResult = !Objects.nonNull(resultMsg) || resultMsg.toString().length() <= maxLength;
        // 当resultMsg是一个超大的集合对象时toString()执行会影响性能
        long costTime = Duration.between(start, LocalDateTime.now()).toMillis();
        StringBuilder sbRsp = new StringBuilder(1000);
        sbRsp.append(System.lineSeparator()).append("-----------------------------控制层日响应志结束调用:-----------------------------").append(System.lineSeparator());
        sbRsp.append("Controller: ").append(pjp.getSignature().getDeclaringType()).append(".")
                .append(pjp.getSignature().getName()).append(System.lineSeparator());
        if (hasContext) {
            sbRsp.append("URI       : ").append(request.getMethod()).append(" ").append(request.getRequestURI())
                    .append(System.lineSeparator());
        }
        sbRsp.append("RequestBody    : ").append(getBody(pjp)).append(System.lineSeparator());
        sbRsp.append("costTime  : ").append(costTime).append(" ms\n");
        sbRsp.append("ResponseBy    : ").append(printResult && Objects.nonNull(resultMsg) ? JacksonUtil.toJson(resultMsg) : null);
        log.info(sbRsp.toString());
    }

    private void logRequest(ProceedingJoinPoint pjp, HttpServletRequest request, boolean hasContext) {
        if (!hasContext) {
            log.warn("当前线程没有上下文,不打印入参");
        }
        StringBuilder sb = new StringBuilder(1000);
        sb.append(System.lineSeparator()).append("-----------------------------控制层日响应志开始调用:-----------------------------").append(System.lineSeparator());
        sb.append("Controller: ").append(pjp.getSignature().getDeclaringType()).append(".")
                .append(pjp.getSignature().getName()).append(System.lineSeparator());
        if (hasContext) {
            String params = buildParams(request);
            sb.append("URI       : ").append(request.getMethod()).append(" ").append(request.getProtocol()).append(" ")
                    .append(request.getRequestURI()).append(params).append(System.lineSeparator());
            sb.append("Header    : {").append(System.lineSeparator());
            Enumeration<String> requestHeader = request.getHeaderNames();
            while (requestHeader.hasMoreElements()) {
                String headerKey = requestHeader.nextElement();
                // 打印所有Header值
                sb.append("    ").append(headerKey).append(" : ").append(request.getHeader(headerKey)).append(System.lineSeparator());
            }
        }
        sb.append("}").append(System.lineSeparator());
        sb.append("RequestBody    : ").append(getBody(pjp));
        // 记录下请求内容
        log.info(sb.toString());
    }

    private String buildParams(HttpServletRequest request) {
        StringBuilder params = new StringBuilder("?");
        // 获取URL参数
        Enumeration<String> names = request.getParameterNames();
        if (request.getMethod().equals(WebContentGenerator.METHOD_GET)) {
            while (names.hasMoreElements()) {
                String name = names.nextElement();
                params.append(name);
                params.append("=");
                params.append(request.getParameter(name));
                params.append("&");
            }
        }
        if (params.length() > 0) {
            params.delete(params.length() - 1, params.length());
        }
        return params.toString();
    }

    private StringBuilder getBody(JoinPoint joinPoint) {
        StringBuilder params = new StringBuilder();
        for (Object arg : joinPoint.getArgs()) {
            try {
                if (!(arg instanceof ServletRequest)
                        && !(arg instanceof ServletResponse)
                        && !(arg instanceof MultipartFile)
                        && !(arg instanceof ModelAndView)
                        && !(arg instanceof Model)) {
                    params.append(", ");
                    params.append(JacksonUtil.toJson(arg));
                }
            } catch (Exception e) {
                log.error("未知类型转换错误:" + arg.getClass().getSimpleName());
            }
        }
        return params;
    }
}